package org.smartjava.concurrent.juc.atomic;


import org.smartjava.util.PrintUtil;
import org.smartjava.util.ThreadUtil;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;

/**
 * {@link AtomicReference#accumulateAndGet(Object, BinaryOperator)}
 * {@link AtomicReference#getAndAccumulate(Object, BinaryOperator)}
 * {@link AtomicReference#weakCompareAndSet(Object, Object)}
 * {@link AtomicReference#compareAndSet(Object, Object)}
 * {@link AtomicReference#getAndSet(Object)}
 * {@link AtomicReference#getAndUpdate(UnaryOperator)}
 * {@link AtomicReference#updateAndGet(UnaryOperator)}
 * {@link AtomicReference#lazySet(Object)}
 * {@link AtomicReference#get()}
 * {@link AtomicReference#set(Object)}
 */
public class AtomicReferenceExample {
    static volatile Record rec = new Record("Nobody", 1000);
    static volatile Record rec2 = new Record("Nobody", 1000);
    static volatile AtomicReference<Record> atomicRec = new AtomicReference<>(new Record("Nobody", 1000));

    public static void main(String[] args) {
        //new Thread(AtomicReferenceExample::addAndMinusMoney).start();
        //new Thread(AtomicReferenceExample::addAndMinusMoneySyn).start();
        new Thread(AtomicReferenceExample::addAndMinusMoneyAtomic).start();
    }

    static void addAndMinusMoney() {
        int people = 5000;
        CountDownLatch countDownLatch = new CountDownLatch(2 * 1000);
        for (int j = 0; j < people; j++) {
            new Thread(() -> {
                rec = new Record(ThreadUtil.threadName(), rec.money + 10);
                countDownLatch.countDown();
            }, "Add-" + j).start();
        }
        for (int j = 0; j < people; j++) {
            new Thread(() -> {
                rec = new Record(ThreadUtil.threadName(), rec.money - 10);
                countDownLatch.countDown();
            }, "Add-" + j).start();
        }
        ThreadUtil.await(countDownLatch);
        PrintUtil.print("addAndMinusMoney():" + rec);
    }

    static void addAndMinusMoneySyn() {
        //人数为存取各5000人
        int people = 5000;
        CountDownLatch countDownLatch = new CountDownLatch(2 * people);
        //存钱的人陆续多线程存钱
        for (int j = 0; j < people; j++) {
            new Thread(() -> {
                synchronized (Object.class) {
                    rec = new Record(ThreadUtil.threadName(), rec.money + 10);
                }
                countDownLatch.countDown();
            }, "Add-" + j).start();
        }
        //取钱的人陆续多线程取钱
        for (int j = 0; j < people; j++) {
            new Thread(() -> {
                synchronized (Object.class) {
                    rec = new Record(ThreadUtil.threadName(), rec.money - 10);
                }
                countDownLatch.countDown();
            }, "Minus-" + j).start();
        }
        ThreadUtil.await(countDownLatch);
        PrintUtil.print("addAndMinusMoneySyn():" + rec);
    }

    static void addAndMinusMoneyAtomic() {
        //人数为存取各5000人
        int people = 5000;
        CountDownLatch countDownLatch = new CountDownLatch(2 * people);

        for (int j = 0; j < people; j++) {
            //取钱的人陆续多线程取钱
            new Thread(() -> {
                atomicRec.updateAndGet(r -> new Record(ThreadUtil.threadName(), r.money - 10));
                countDownLatch.countDown();
            }, "Minus-" + j).start();
            //存钱的人陆续多线程存钱
            new Thread(() -> {
                //原子更新Record对象
                atomicRec.updateAndGet(r -> new Record(ThreadUtil.threadName(), r.money + 10));
                countDownLatch.countDown();
            }, "Add-" + j).start();
        }
        ThreadUtil.await(countDownLatch);
        PrintUtil.print("addAndMinusMoneyAtomic():" + atomicRec);
    }


    static String threadName() {
        return Thread.currentThread().getName();
    }


}

class Record {
    public String name;
    public int money;

    Record(String name, int money) {
        this.name = name;
        this.money = money;
    }

    @Override
    public String toString() {
        return "Record{" +
                "name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
